/*
 * Copyright (C) 2008,2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Prototypes
 */
static evector_t *
get_temp_name(eint32_t offset);

/*
 * Initialization
 */
static ehash_t *compose;

/*
 * Implementation
 */
void
init_symbol(void)
{
    enew_hash((eobject_t *)&compose, H_pointer, 5);
    eadd_root((eobject_t *)&compose);
}

esymbol_t *
eget_symbol(ehash_t *hash, evector_t *name)
{
    return ((esymbol_t *)eget_hash(hash, name));
}

esymbol_t *
enew_symbol(ehash_t *hash, evector_t *name)
{
    esymbol_t	*symbol;

#if DEBUG
    if (etype(name) != (t_const|t_string))
	eparse_error(null, "internal error (bad string)");
#endif

    symbol = eget_symbol(hash, name);
    if (symbol == null) {
	enew(&symbol, symbol);
	symbol->name = name;
	symbol->hash = hash;
	eput_hash(hash, (eentry_t *)symbol);
    }

    return (symbol);
}

esymbol_t *
echk_bound_symbol(esymbol_t *name)
{
    ehash_t	*hash;
    esymbol_t	*symbol;

    if (etype(name) != t_symbol)
	eparse_error(null, "expecting symbol");

    /* descend from current namespace searching for matching symbol */
    hash = ht_current;
    while (hash) {
	/* hash->memory is set for class members, because the
	 * hash entries are both, the key and the value */
	if (hash->memory) {
	    if ((symbol = eget_symbol(hash, name->name)))
		break;
	    if (fn_current->record &&
		(symbol = eget_symbol(fn_current->record->methods,
				      name->name)))
		break;
	}
	else if ((symbol = (esymbol_t *)
		  eget_hash_entry(hash, (eentry_t *)name)))
	    break;
	hash = hash->parent;
    }

    return (hash ? symbol : null);
}

esymbol_t *
eget_bound_symbol(esymbol_t *name)
{
    esymbol_t	*symbol;

    if ((symbol = echk_bound_symbol(name)) == null)
	eparse_error(null, "undefined symbol '%p'", name->name);

    return (symbol);
}

esymbol_t *
enew_bound_symbol(esymbol_t *name)
{
    esymbol_t	*symbol;

    if (etype(name) != t_symbol)
	eparse_error(null, "expecting symbol");

    /* symbol must not exist in current namespace */
    if ((symbol = (esymbol_t *)eget_hash_entry(ht_current,
					       (eentry_t *)name)) == null)
	symbol = enew_symbol(ht_current, name->name);
    else if (symbol->a_symbol)
	eparse_error(null, "symbol '%p' already defined", symbol->name);

    return (symbol);
}

esymbol_t *
enew_temp_symbol(efunction_t *function)
{
    esymbol_t	*symbol;

    symbol = enew_symbol(function->hash, get_temp_name(function->temps++));
    symbol->offset = function->hash->offset++;
    symbol->a_symbol = true;
    symbol->a_expression = true;

    return (symbol);
}

void
erem_temp_symbol(efunction_t *function, esymbol_t *symbol)
{
    eint32_t	 i;
    evector_t	*name;
    evector_t	*temp;
    eint32_t	 offset;

    offset = symbol->offset;

    name = symbol->name;
    erem_hash(function->hash, (eentry_t *)symbol);
    --function->stack;
    --function->temps;
    --function->hash->offset;

    /* if there are symbols with a higher offset, update code
     * representation by actually "renaming" the symbol */
    for (offset++; offset <= function->hash->offset; offset++) {
	for (i = 0; i < function->hash->size; i++) {
	    symbol = (esymbol_t *)function->hash->entries[i];
	    for (; symbol; symbol = symbol->next) {

		/* static symbols not yet implemented, but once
		 * implemented, the offset should point to an
		 * entry in the globals symbol table */
		if (symbol->offset == offset && !(symbol->a_static)) {

		    /* temporarily remove from hash table */
		    erem_hash(function->hash, (eentry_t *)symbol);

		    /* adjust name and offset */
		    --symbol->offset;
		    temp = symbol->name;
		    symbol->name = name;
		    name = temp;

		    /* add back to hash table */
		    eput_hash(function->hash, (eentry_t *)symbol);

		    /* the rename above avoids the need of parsing the
		     * code representation to adjust the symbol pointer */
		    goto done;
		}
	    }
	}
    done:;
    }
}

static evector_t *
get_temp_name(eint32_t offset)
{
    eint32_t	count;
    eint32_t	length;
    euint8_t	name[32];

    /* create unique name */
    name[0] = '@';
    for (length = 1, count = offset; count >= 10; count /= 10)
	++length;
    ++length;

    for (count = length - 1; offset >= 10; offset /= 10)
	name[count--] = offset % 10 + '0';
    name[count] = offset + '0';

    return (eget_string(name, length));
}
